/**
 * Project: Utilities <br>
 * Package: org.leumasjaffe.functional
 */
package org.leumasjaffe.functional;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
 * Class org.leumasjaffe.functional..Compose in file Compose.java <br>
 * 
 * @author leumasjaffe
 * @version Dec 12, 2013
 * Created on: Dec 12, 2013
 * 
 * @param <T>
 * @param <R>
 */
@NonNullByDefault
public class Compose<T, R> implements UnaryFunction<T, R> {
	private final List<UnaryFunction<?, ?>> ops = new LinkedList<UnaryFunction<?, ?>>();
	
	/**
	 * Constructs a function composition with the property Compose.apply(t) = g.apply(f.apply(t))
	 * @param f The first function
	 * @param g The second function
	 */
	private <S> Compose(UnaryFunction<T, ? extends S> f, UnaryFunction<? super S, R> g) {
		this.ops.add(f);
		this.ops.add(g);
	}

	/**
	 * Constructs a function composition with the property Compose.apply(t) = g.apply(f.apply(t))
	 * @param f The first function
	 * @param g The second function
	 */
	private <S> Compose(UnaryFunction<T, ? extends S> f, Compose<? super S, R> g) {
		this.ops.add(f);
		this.ops.addAll(g.ops);
	}

	/**
	 * Constructs a function composition with the property Compose.apply(t) = g.apply(f.apply(t))
	 * @param f The first function
	 * @param g The second function
	 */
	private <S> Compose(Compose<T, ? extends S> f, UnaryFunction<? super S, R> g) {
		this.ops.addAll(f.ops);
		this.ops.add(g);
	}

	/**
	 * Constructs a function composition with the property Compose.apply(t) = g.apply(f.apply(t))
	 * @param f The first function
	 * @param g The second function
	 */
	private <S> Compose(Compose<T, ? extends S> f, Compose<? super S, R> g) {
		this.ops.addAll(f.ops);
		this.ops.addAll(g.ops);
	}
	
	/**
	 * Creates a composition of two functions such that Compose.apply(t) = g.apply(f.apply(t))
	 * @param f The first function
	 * @param g The second function
	 * @param <T> The initial type that will be used as an input
	 * @param <S> An intermediate type that connects the two functions
	 * @param <R> The final return type of the function composition
	 * @return A UnaryFunction of type Compose<T, R> that will apply the given functions
	 * to an input in sequence.
	 */
	public static final <T, S, R> UnaryFunction<T, R> of(UnaryFunction<T, ? extends S> f,
			UnaryFunction<? super S, R> g) {
		Compose<T, R> toReturn = null;
		if (f instanceof Compose) {
			if (g instanceof Compose) {
				toReturn = new Compose<T, R>((Compose<T, ? extends S>) f, 
						(Compose<? super S, R>) g);
			} else {
				toReturn = new Compose<T, R>((Compose<T, ? extends S>) f, g);
			}
		} else if (g instanceof Compose) {
			toReturn = new Compose<T, R>(f, (Compose<? super S, R>) g);
		} else {
			toReturn = new Compose<T, R>(f, g);
		}
		return toReturn;
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.UnaryFunction#apply(java.lang.Object)
	 */
	@SuppressWarnings({ "unchecked", "null" })
	@Override
	public R apply(T entry) {
		final Iterator<UnaryFunction<?, ?>> it = this.ops.iterator();
		return it.hasNext() ? 
				tailApply(it, (UnaryFunction<T, ?>) it.next(), entry) :
					(R) entry; // $codepro.audit.disable unnecessaryCast
	}

	/**
	 * @param it
	 * @param next
	 * @param entry
	 * @return
	 */
	// f : A -> B
	// g : B -> C
	// c : A -> C := g f
	// c.list = {f, g}
	// apply(a) => tailApply(it(1), f, a) => tailApply(it(END), g, f(a)) => g(f(a))
	@SuppressWarnings({ "unchecked", "null" })
	private <P, S> R tailApply(Iterator<UnaryFunction<?, ?>> it,
			UnaryFunction<P, S> next, P entry) {
		final S nextEntry = next.apply(entry);
		return it.hasNext() ? 
				tailApply(it, (UnaryFunction<S, ?>) it.next(), nextEntry) : 
					(R) nextEntry; // $codepro.audit.disable unnecessaryCast
	}

}
